#import "DDXML.h"


// We can't rely solely on NSAssert, because many developers disable them for release builds.
// Our API contract requires us to keep these assertions intact.
#define DDXMLAssert(condition, desc, ...)                                                                 \
do{                                                                                                     \
if(!(condition)) {                                                                                    \
[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd                                     \
object:self                                     \
file:[NSString stringWithUTF8String:__FILE__] \
lineNumber:__LINE__                                 \
description:(desc), ##__VA_ARGS__];                  \
}                                                                                                     \
}while(NO)


// Create assertion to ensure xml node is not a zombie.
#if DDXML_DEBUG_MEMORY_ISSUES
#define DDXMLNotZombieAssert()                                                                            \
do{                                                                                                     \
if(DDXMLIsZombie(genericPtr, self)) {                                                                      \
NSString *desc = @"XML node is a zombie - It's parent structure has been freed!";                   \
[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd                                     \
object:self                                     \
file:[NSString stringWithUTF8String:__FILE__] \
lineNumber:__LINE__                                 \
description:desc];                                   \
}                                                                                                     \
}while(NO)
#endif

#define DDLastErrorKey @"DDXML:LastError"



/**
 * DDXMLNode can represent several underlying types, such as xmlNodePtr, xmlDocPtr, xmlAttrPtr, xmlNsPtr, etc.
 * All of these are pointers to structures, and all of those structures start with a pointer, and a type.
 * The xmlKind struct is used as a generic structure, and a stepping stone.
 * We use it to check the type of a structure, and then perform the appropriate cast.
 *
 * For example:
 * if(genericPtr->type == XML_ATTRIBUTE_NODE)
 * {
 *     xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
 *     // Do something with attr
 * }
 **/
struct _xmlKind {
    void * ignore;
    xmlElementType type;
};
typedef struct _xmlKind *xmlKindPtr;

/**
 * Most xml types all start with this standard structure. In fact, all do except the xmlNsPtr.
 * We will occasionally take advantage of this to simplify code when the code wouldn't vary from type to type.
 * Obviously, you cannnot cast a xmlNsPtr to a xmlStdPtr.
 **/
struct _xmlStd {
    void * _private;
    xmlElementType type;
    const xmlChar *name;
    struct _xmlNode *children;
    struct _xmlNode *last;
    struct _xmlNode *parent;
    struct _xmlStd *next;
    struct _xmlStd *prev;
    struct _xmlDoc *doc;
};
typedef struct _xmlStd *xmlStdPtr;


NS_INLINE BOOL IsXmlAttrPtr(void *kindPtr)
{
    return ((xmlKindPtr)kindPtr)->type == XML_ATTRIBUTE_NODE;
}

NS_INLINE BOOL IsXmlNodePtr(void *kindPtr)
{
    switch (((xmlKindPtr)kindPtr)->type)
    {
        case XML_ELEMENT_NODE       :
        case XML_PI_NODE            :
        case XML_COMMENT_NODE       :
        case XML_TEXT_NODE          :
        case XML_CDATA_SECTION_NODE : return YES;
        default                     : return NO;
    }
}

NS_INLINE BOOL IsXmlDocPtr(void *kindPtr)
{
    return ((xmlKindPtr)kindPtr)->type == XML_DOCUMENT_NODE;
}

NS_INLINE BOOL IsXmlDtdPtr(void *kindPtr)
{
    return ((xmlKindPtr)kindPtr)->type == XML_DTD_NODE;
}

NS_INLINE BOOL IsXmlNsPtr(void *kindPtr)
{
    return ((xmlKindPtr)kindPtr)->type == XML_NAMESPACE_DECL;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@interface DDXMLNamespaceNode : DDXMLNode
    {
        // The xmlNsPtr type doesn't store a reference to it's parent.
        // This is here to fix the problem, and make this class more compatible with the NSXML classes.
        xmlNodePtr nsParentPtr;
    }
    
+ (id)nodeWithNsPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent owner:(DDXMLNode *)owner;
- (id)initWithNsPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent owner:(DDXMLNode *)owner;
    
- (xmlNodePtr)_nsParentPtr;
- (void)_setNsParentPtr:(xmlNodePtr)parentPtr;
    
    // Overrides several methods in DDXMLNode
    
    @end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@interface DDXMLAttributeNode : DDXMLNode
    {
        // The xmlAttrPtr type doesn't allow for ownership of a namespace.
        //
        // In other types, such as xmlNodePtr:
        // - nsDef stores namespaces that are owned by the node (have been alloced by the node).
        // - ns is simply a pointer to the default namespace of the node, which may or may not reside in its own nsDef list.
        //
        // The xmlAttrPtr only has a ns, it doesn't have a nsDef list.
        // Which completely makes sense really, since namespaces have to be defined elsewhere.
        //
        // This is here to maintain compatibility with the NSXML classes,
        // where one can assign a namespace to an attribute independently.
        xmlNsPtr attrNsPtr;
    }
    
+ (id)nodeWithAttrPrimitive:(xmlAttrPtr)attr owner:(DDXMLNode *)owner;
- (id)initWithAttrPrimitive:(xmlAttrPtr)attr owner:(DDXMLNode *)owner;
    
    // Overrides several methods in DDXMLNode
    
    @end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@interface DDXMLInvalidNode : DDXMLNode
    {
    }
    
    // Overrides several methods in DDXMLNode
    
    @end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@interface DDXMLNode (PrivateAPI)
    
+ (id)nodeWithUnknownPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner;
    
+ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner;
- (id)initWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner;
    
- (BOOL)_hasParent;
    
+ (void)getHasPrefix:(BOOL *)hasPrefixPtr localName:(NSString **)localNamePtr forName:(NSString *)name;
+ (void)getPrefix:(NSString **)prefixPtr localName:(NSString **)localNamePtr forName:(NSString *)name;
    
+ (void)recursiveStripDocPointersFromNode:(xmlNodePtr)node;
    
+ (void)detachNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node;
+ (void)removeNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node;
+ (void)removeAllNamespacesFromNode:(xmlNodePtr)node;
    
+ (void)detachAttribute:(xmlAttrPtr)attr andClean:(BOOL)clean;
+ (void)detachAttribute:(xmlAttrPtr)attr;
+ (void)removeAttribute:(xmlAttrPtr)attr;
+ (void)removeAllAttributesFromNode:(xmlNodePtr)node;
    
+ (void)detachChild:(xmlNodePtr)child andClean:(BOOL)clean andFixNamespaces:(BOOL)fixNamespaces;
+ (void)detachChild:(xmlNodePtr)child;
+ (void)removeChild:(xmlNodePtr)child;
+ (void)removeAllChildrenFromNode:(xmlNodePtr)node;
    
    BOOL DDXMLIsZombie(void *xmlPtr, DDXMLNode *wrapper);
    
+ (NSError *)lastError;
    
    @end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@interface DDXMLElement (PrivateAPI)
    
+ (id)nodeWithElementPrimitive:(xmlNodePtr)node owner:(DDXMLNode *)owner;
- (id)initWithElementPrimitive:(xmlNodePtr)node owner:(DDXMLNode *)owner;
    
- (DDXMLNode *)_recursiveResolveNamespaceForPrefix:(NSString *)prefix atNode:(xmlNodePtr)nodePtr;
- (NSString *)_recursiveResolvePrefixForURI:(NSString *)uri atNode:(xmlNodePtr)nodePtr;
    
    @end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@interface DDXMLDocument (PrivateAPI)
    
+ (id)nodeWithDocPrimitive:(xmlDocPtr)doc owner:(DDXMLNode *)owner;
- (id)initWithDocPrimitive:(xmlDocPtr)doc owner:(DDXMLNode *)owner;
    
    @end
